package lmscript.postscript.syntax;

import lmscript.postscript.syntax.stack.OperandStack;

public abstract class ArithmeticOperator extends PSOperator {

	protected PSRandom random = new PSRandom();

	public static Class[] operators = new Class[] { Add.class, Div.class, IDiv.class, Mod.class,
			Mul.class, Sub.class, Abs.class, Neg.class, Ceiling.class, Floor.class, Round.class,
			Truncate.class, Sqrt.class, Atan.class, Cos.class, Sin.class, Exp.class, Ln.class,
			Log.class, Rand.class, SRand.class, RRand.class };

	public ArithmeticOperator(String name) {
		super(name);
	}
}

class Add extends ArithmeticOperator {
	public Add() {
		super("add");
		operandTypes = new Class[] { PSNumber.class, PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n2 = os.popNumber().getNumber();
		Number n1 = os.popNumber().getNumber();

		Number result;
		if (n1 instanceof Double || n2 instanceof Double) {
			result = new Double(n1.doubleValue() + n2.doubleValue());
		} else {
			result = new Integer(n1.intValue() + n2.intValue());
		}
		os.pushNumber(result);

		return true;
	}
}

class Div extends ArithmeticOperator {

	public Div() {
		super("div");
		operandTypes = new Class[] { PSNumber.class, PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n2 = os.popNumber().getNumber();
		Number n1 = os.popNumber().getNumber();

		Number result;
		if (!(n2.doubleValue() > 0) && !(n2.doubleValue() < 0)) {
			throwError(os, new UndefinedResult());

		}
		result = new Double(n1.doubleValue() / n2.doubleValue());
		os.pushNumber(result);

		return true;
	}
}

class IDiv extends ArithmeticOperator {

	public IDiv() {
		super("idiv");
		operandTypes = new Class[] { PSInteger.class, PSInteger.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n2 = os.popNumber().getNumber();
		Number n1 = os.popNumber().getNumber();

		Number result;
		if (n2.intValue() == 0) {
			throwError(os, new UndefinedResult());

		}
		result = new Integer(n1.intValue() / n2.intValue());
		os.pushNumber(result);

		return true;
	}
}

class Mod extends ArithmeticOperator {

	public Mod() {
		super("mod");
		operandTypes = new Class[] { PSInteger.class, PSInteger.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n2 = os.popNumber().getNumber();
		Number n1 = os.popNumber().getNumber();

		Number result;
		if (n2.intValue() == 0) {
			throwError(os, new UndefinedResult());

		}
		result = new Integer(n1.intValue() % n2.intValue());
		os.pushNumber(result);

		return true;
	}
}

class Mul extends ArithmeticOperator {

	public Mul() {
		super("mul");
		operandTypes = new Class[] { PSNumber.class, PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n2 = os.popNumber().getNumber();
		Number n1 = os.popNumber().getNumber();

		Number result;
		if (n1 instanceof Double || n2 instanceof Double) {
			result = new Double(n1.doubleValue() * n2.doubleValue());
		} else {
			Long l = new Long(n1.intValue() * n2.intValue());
			if (l > Integer.MAX_VALUE || l < Integer.MIN_VALUE) {
				result = new Double(n1.doubleValue() * n2.doubleValue());
			} else {
				result = new Integer(l.intValue());
			}
		}
		os.pushNumber(result);
		return true;
	}
}

class Sub extends ArithmeticOperator {

	public Sub() {
		super("sub");
		operandTypes = new Class[] { PSNumber.class, PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n2 = os.popNumber().getNumber();
		Number n1 = os.popNumber().getNumber();

		Number result;
		if (n1 instanceof Double || n2 instanceof Double) {
			result = new Double(n1.doubleValue() - n2.doubleValue());
		} else {
			Long l = new Long(n1.intValue() - n2.intValue());
			if (l > Integer.MAX_VALUE || l < Integer.MIN_VALUE) {
				result = new Double(n1.doubleValue() - n2.doubleValue());
			} else {
				result = new Integer(l.intValue());
			}
		}
		os.pushNumber(result);
		return true;
	}
}

class Abs extends ArithmeticOperator {

	public Abs() {
		super("abs");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result;
		if (n1 instanceof Double) {
			result = new Double(Math.abs(n1.doubleValue()));
		} else {
			result = new Integer(Math.abs(n1.intValue()));
		}
		os.pushNumber(result);
		return true;
	}
}

class Neg extends ArithmeticOperator {

	public Neg() {
		super("neg");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result;
		if (n1 instanceof Double) {
			result = new Double(-n1.doubleValue());
		} else {
			result = new Integer(-n1.intValue());
		}
		os.pushNumber(result);
		return true;
	}
}

class Ceiling extends ArithmeticOperator {

	public Ceiling() {
		super("ceiling");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result = new Double(Math.ceil(n1.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Floor extends ArithmeticOperator {

	public Floor(String name) {
		super("floor");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result = new Double(Math.floor(n1.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Round extends ArithmeticOperator {

	public Round() {
		super("round");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result = new Double(Math.round(n1.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Truncate extends ArithmeticOperator {

	public Truncate() {
		super("truncate");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result;
		if (n1 instanceof Double) {
			result = new Double(Math.rint(n1.doubleValue()));
		} else {
			result = new Integer(n1.intValue());
		}

		os.pushNumber(result);
		return true;
	}
}

class Sqrt extends ArithmeticOperator {

	public Sqrt() {
		super("sqrt");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result = new Double(Math.sqrt(n1.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Atan extends ArithmeticOperator {

	public Atan() {
		super("atan");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result = new Double(Math.atan(n1.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Cos extends ArithmeticOperator {

	public Cos() {
		super("cos");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result = new Double(Math.cos(n1.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Sin extends ArithmeticOperator {

	public Sin() {
		super("sin");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		Number result = new Double(Math.sin(n1.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Exp extends ArithmeticOperator {

	public Exp() {
		super("exp");
		operandTypes = new Class[] { PSNumber.class, PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number exponent = os.popNumber().getNumber();
		Number base = os.popNumber().getNumber();

		Number result = new Double(Math.pow(base.doubleValue(), exponent.doubleValue()));
		os.pushNumber(result);
		return true;
	}
}

class Ln extends ArithmeticOperator {

	public Ln() {
		super("ln");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		if (n1.doubleValue() <= 0.0) {
			throwError(os, new RangeCheck());
		} else {
			Number result = new Double(Math.log(n1.doubleValue()));
			os.pushNumber(result);
		}
		return true;
	}
}

class Log extends ArithmeticOperator {

	public Log() {
		super("log");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		if (n1.doubleValue() <= 0.0) {
			throwError(os, new RangeCheck());
		} else {
			Number result = Math.log(n1.doubleValue()) / Math.log(10.0);
			os.pushNumber(result);
		}
		return true;
	}
}

class Rand extends ArithmeticOperator {

	public Rand(String name) {
		super("rand");
	}

	@Override
	public boolean execute(OperandStack os) {
		Number result = random.nextInt();
		os.pushNumber(result);
		return true;
	}
}

class SRand extends ArithmeticOperator {

	public SRand(String name) {
		super("srand");
		operandTypes = new Class[] { PSNumber.class };
	}

	@Override
	public boolean execute(OperandStack os) {
		Number n1 = os.popNumber().getNumber();
		random.setSeed(n1.intValue());
		return true;
	}
}

class RRand extends ArithmeticOperator {

	public RRand(String name) {
		super("rrand");
	}

	@Override
	public boolean execute(OperandStack os) {
		Number result = random.getSeed();
		os.pushNumber(result);
		return true;
	}
}